﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;
using System.Timers;

namespace LanderWpf
{
    public enum LandingSearch
    {
        BruteForce,
        Informed
    }

    public class Agent
    {
        private Lander lander;

        private int wait;
        private bool cycled;
        private System.Timers.Timer timer;

        public LandingSearch SearchType { get; set; }


        public Agent(Lander l, LandingSearch searchType = LandingSearch.BruteForce, int w = 200)
        {
            lander = l;
            wait = w;
            cycled = false;
            SearchType = searchType;
        }

        public void Start()
        {
            timer = new System.Timers.Timer(wait);
            timer.Elapsed += (s, e) => { Run(); };
            timer.Start();
        }

        public void Stop()
        {
            timer.Stop();
        }

        private void Run()
        {
            App.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    if (lander.Status == FlightStatus.Flying)
                    {
                        // Layer one: Adjusting Attitude
                        AdjustAttitude();

                        // Layer two: Finding a landing spot
                        FindLandingSpot();

                        // Layer three: Descent
                        Descend();

                        MainWindow.Current.UpdateCrashed();
                        MainWindow.Current.UpdateMapCanvas();
                        MainWindow.Current.UpdateHeightCanvas();

                        cycled = false;
                    }
                    else
                    {
                        timer.Stop();
                    }
                }));
        }

        private void AdjustAttitude()
        {
            if (!cycled)
            {
                if ((lander.Pitch <= 10 || lander.Pitch >= 350) &&
                     (lander.Roll <= 10 || lander.Roll >= 350))
                {
                    // attitude is ok, do nothing
                }
                else if (lander.Pitch > 10 && lander.Pitch < 350)
                {
                    // need to adjust pitch
                    if (lander.Pitch < 180) lander.PitchAft();
                    else lander.PitchFore();

                    cycled = true;
                }
                else if (lander.Roll > 10 && lander.Roll < 350)
                {
                    // need to adjust roll
                    if (lander.Roll > 180) lander.RollLeft();
                    else lander.RollRight();

                    cycled = true;
                }
            }
        }

        private void FindLandingSpot()
        {
            if (!cycled)
            {
                if (lander.Below == SurfaceType.Smooth)
                {
                    // landing spot is ok, do nothing
                }
                else
                {
                    if (SearchType == LandingSearch.BruteForce)
                        FindLandingSpotBruteForce();
                    else
                        FindLandingSpotInformed();

                    cycled = true;
                }
            }
        }

        private void FindLandingSpotBruteForce()
        {
            // randomly choose a direction
            Random rng = new Random();
            int dir = rng.Next(4);
            switch (dir)
            {
                case 0:
                    // down
                    lander.SurgeFore();
                    break;
                case 1:
                    // up
                    lander.SurgeAft();
                    break;
                case 2:
                    // left
                    lander.SwayLeft();
                    break;
                case 3:
                    // right
                    lander.SwayRight();
                    break;
            }
        }

        private void FindLandingSpotInformed()
        {
            // find nearest landing spot

            int? lat = null;
            int? lon = null;

            int w = lander.Surface.GetLength(0); // width
            int h = lander.Surface.GetLength(1); // height

            for (int x = 0; x < w; ++x)
            {
                for (int y = 0; y < h; ++y)
                {
                    if (lander.Surface[x, y] == SurfaceType.Smooth)
                    {
                        bool valueAssigned = lat.HasValue && lon.HasValue;
                        if (valueAssigned)
                        {
                            int currDistance = Distance(lat.Value, lon.Value, lander.Lattitude, lander.Longitude);
                            int newDistance = Distance(x, y, lander.Lattitude, lander.Longitude);
                            if (currDistance > newDistance)
                            {
                                // new is best
                                lat = x;
                                lon = y;
                            }
                        }
                        else
                        {
                            // nothing assigned yet
                            lat = x;
                            lon = y;
                        }
                    }
                }
            }

            // go there!
            if (lat > lander.Lattitude) lander.SurgeFore();
            else if (lat < lander.Lattitude) lander.SurgeAft();
            else if (lon > lander.Longitude) lander.SwayLeft();
            else if (lon < lander.Longitude) lander.SwayRight();

            cycled = true;
        }

        /// <summary>
        /// Return Manhattan distance between 2 points
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <returns></returns>
        private int Distance(int x1, int y1, int x2, int y2)
        {
            int dx = Math.Abs(x1 - x2);
            int dy = Math.Abs(y1 - y2);
            
            // Manhattan Distance
            return dx + dy;
        }

        private void FindLandingSpotOld()
        {
            if (!cycled)
            {
                if (lander.Below == SurfaceType.Smooth)
                {
                    // landing spot is ok, do nothing
                }
                else
                {
                    // find safe landing spot in map

                    int lat = 0;
                    int lon = 0;

                    int w = lander.Surface.GetLength(0); // width
                    int h = lander.Surface.GetLength(1); // height

                    for (int x = 0; x < w; ++x)
                    {
                        for (int y = 0; y < h; ++y)
                        {
                            if (lander.Surface[x, y] == SurfaceType.Smooth)
                            {
                                lat = x;
                                lon = y;

                                goto end;
                            }
                        }
                    }
                end:

                    if (lat > lander.Lattitude) lander.SurgeFore();
                    else if (lat < lander.Lattitude) lander.SurgeAft();
                    else if (lon > lander.Longitude) lander.SwayLeft();
                    else if (lon < lander.Longitude) lander.SwayRight();

                    cycled = true;
                }
            }
        }

        private void Descend()
        {
            if (!cycled)
            {
                if (lander.Altitude > 0)
                {
                    lander.Descend();
                    cycled = true;
                }
            }
        }
    }
}
